%{
#include <iostream>
#include <cstdio>
#include <map>
#include <deque>
#include <cstring>
#include <stack>
#include <string>
#include <sstream>
#include "declarations.h"

using namespace std;\

extern int yylex();
extern FILE *yyin;
extern int lineNumber;

void yyerror(const char* s) { 
	fprintf(stderr, "ERROR(line %i)-> Syntax error\n", lineNumber);
	exit(-1);
}

symbol_table table;
function_table functions;
int tmpId = 0;
int ifId = 0, forId = 0, whileId = 0, blockId = 0;
const char *tmpPre = "tmp";


string currFunctionDecl = "";
string currFunctionCall = "";

stack<string> breakLabel;
vector<type_name> callFunctionParams;

FILE *output;

%}

%union
{	
	struct values_struct values;
	int id;
}

%token VOID INT DOUBLE BOOL STRING INULL
%token WHILE FOR IF ELSE RETURN BREAK PRINT READINT READLINE
%token TRUE FALSE
%token LESSEQTHAN MOREEQTHAN  EQUALCOMP DIFFCOMP
%token AND OR
%token<values> CHEX CDEC CSTRING CDOUBLE
%token<values> ID 

%type<values> constant variable type expr valueL startScope functionName beginCall call list_instPrint void

%type<id> ifCond else whileStart whileCond forStart forCond forInc

%left '='
%left OR
%left AND
%left EQUALCOMP DIFFCOMP
%left '<' LESSEQTHAN '>' MOREEQTHAN
%left '+' '-'
%left '*' '/' '%'
%left '!'
%left '['

%nonassoc LOWER_THAN_ELSE
%nonassoc ELSE

%start program


%%

program:	decl
        | 	program  decl
        ;

decl:       variableDecl
        | 	functionDecl 
        ;

variableDecl:	variable ';'
        ;

variable: 	type ID
			{ 
				validateVariableDeclaration($1, $2, $$, table);	
				strcpy($$.name, $2.name);
				fprintf(output, "\tvar %s\n", $$.name);			
			}
        |   type ID '[' CDEC ']'
			{ 
				validateVariableDeclaration($1, $2, $$, table);	
				strcpy($$.name, $2.name);
				fprintf(output, "\tvar %s\n", $$.name);			
			}
        ;

type:        INT 	{ $$.type = int_type; }
        |    DOUBLE { $$.type = double_type; }
        |    BOOL 	{ $$.type = bool_type; }
        |    STRING	{ $$.type = string_type; }
        ;

functionDecl:   functionName '(' formals ')' '{'  scopedBlockList '}' { 					
					currFunctionDecl = "";					
					fprintf(output, "label end_func_%s:\n", $1.name); 
					popScope(table); 
				}
		|    	functionName '(' ')' '{' scopedBlockList '}' { 
					currFunctionDecl = "";
					fprintf(output, "label end_func_%s:\n", $1.name); 
					popScope(table); 
				}
        ;

functionName: 	type ID	{ 
					validateFunctionDeclaration(table, functions, $1, $2, $$);
					currFunctionDecl = $2.name;
					fprintf(output, "label begin_func_%s:\n", $2.name); 
					strcpy($$.name, $2.name); 
					pushScope(table); 
				}
		|		void ID	{
					validateFunctionDeclaration(table, functions, $1, $2, $$);
					currFunctionDecl = $2.name;
					fprintf(output, "label begin_func_%s:\n", $2.name); 
					strcpy($$.name, $2.name); 
					pushScope(table);
				}
		;

formals:    variable				{ validateFunctionParams(functions, currFunctionDecl, $1); }
        | 	formals ',' variable    { validateFunctionParams(functions, currFunctionDecl, $3); }
        ;

instBlockList: 	startScope instBlock1 instBlock2 '}' { 
					popScope(table); 
					fprintf(output, "label end_%s:\n", $1.name); 
				} 
        | 		startScope '}' { 
        			popScope(table); 
        			fprintf(output, "label end_%s:\n", $1.name); 
        		}
        | 		startScope instBlock1 '}' { 
					popScope(table); 
					fprintf(output, "label end_%s:\n", $1.name); 
				} 
        | 		startScope instBlock2 '}' { 
					popScope(table); 
					fprintf(output, "label end_%s:\n", $1.name); 
				} 
        ;

scopedBlockList: instBlock1 instBlock2 
        |		/* empty */
       	|		instBlock1
       	|		instBlock2
       	;

startScope: 	'{' { 
					sprintf($$.name, "block_%d", blockId++);
					pushScope(table); 
					fprintf(output, "label begin_%s:\n", $$.name);
				}
        ;

instBlock1:    	variableDecl
        | 		instBlock1 variableDecl
        ;

instBlock2:    	inst 
        | 		instBlock2 inst
        ;

inst:       expr ';'
        | 	';'
        |   instIf
        |   instWhile
        |   instFor
        |   instBreak
        |   instReturn
        |   instPrint
        |   instBlockList
        ;

/************* IF *************/

instIf:     ifCond inst%prec LOWER_THAN_ELSE { 
				fprintf(output, "label end_if_%d:\n", $1);
				fprintf(output, "label begin_else_%d:\n", $1);
				fprintf(output, "\tgoto end_if_%d\n", $1);
				fprintf(output, "label end_if_%d:\n", $1); 
			}
        | 	else inst {
        		fprintf(output, "\tgoto end_if_%d\n", $1);
				fprintf(output, "label end_if_%d:\n", $1); 
        	}
        ;


ifCond:		IF '(' expr ')' { 
				validateBoolCond($3);				
				$$ = ifId++;
				fprintf(output, "\tbranch-if %s begin_if_%d begin_else_%d\n", $3.name, $$, $$);
				fprintf(output, "label begin_if_%d:\n", $$);
			}
		;
else:		ifCond inst ELSE {
				$$ = $1;
				fprintf(output, "\tgoto end_if_%d\n", $1);
				fprintf(output, "label begin_else_%d:\n", $1);
			}
		;

/************* WHILE *************/

instWhile:  whileCond inst {
				fprintf(output, "\tgoto while_cond_%d\n", $1);
				fprintf(output, "label while_end_%d:\n", $1);
				breakLabel.pop();
			}
        ;
        
whileStart: WHILE { 
				$$ = whileId++;
				fprintf(output, "label while_cond_%d:\n", $$);
			}
		;

whileCond: 	whileStart '(' expr ')' {
				validateBoolCond($3);				
				$$ = $1;
				fprintf(output, "\tbranch-if %s while_body_%d while_end_%d\n", $3.name, $$, $$);
				fprintf(output, "label while_body_%d:\n", $$);
				
				stringstream ss;
				ss << "while_end_" << $$;
				breakLabel.push(ss.str());
			}
		;

/************* FOR *************/

instFor:    forInc inst {
				fprintf(output, "\tgoto for_inc_%d\n", $1);
				fprintf(output, "label for_end_%d:\n", $1);
				breakLabel.pop();
			}
        ;

forInit:	FOR '(' expr ';' 
		|	FOR '(' ';'
		;

forStart: 	forInit {
				$$ = forId++;
				fprintf(output, "label for_cond_%d:\n", $$);
			}
		;

forCond:	forStart expr ';' {
				validateBoolCond($2);
				$$ = $1;
				fprintf(output, "\tbranch-if %s for_body_%d for_end_%d\n", $2.name, $$, $$);
				fprintf(output, "label for_inc_%d:\n", $$);
			}
		;

forInc:		forCond expr ')' {
				$$ = $1;
				fprintf(output, "\tgoto for_cond_%d\n", $$);
				fprintf(output, "label for_body_%d:\n", $$);
				
				stringstream ss;
				ss << "for_end_" << $$;
				breakLabel.push(ss.str());
			}
		|	forCond ')' {
				$$ = $1;
				fprintf(output, "\tgoto for_cond_%d\n", $$);
				fprintf(output, "label for_body_%d:\n", $$);
				
				stringstream ss;
				ss << "for_end_" << $$;
				breakLabel.push(ss.str());
			}
		;

/************* BREAK *************/

instBreak:	BREAK ';' {
				validateBreak(breakLabel);
				fprintf(output, "\tgoto %s\n", breakLabel.top().c_str());
			}
        ;

/************* CALL *************/

call:       beginCall '(' params_block ')' {
				$$.type = $1.type;
				currFunctionCall = "";
				strcpy($$.name, $1.name);
			}
        | 	beginCall '('  ')' { validateFunctionCallParams(functions, callFunctionParams, currFunctionCall); }
        ;

beginCall: 	ID 	{
					validateFunctionCallName(functions, $1.name, currFunctionDecl);
					$$.type = $1.type;
					currFunctionCall = $1.name;
					fprintf(output, "\tbegin-call\n"); 
					strcpy($$.name, $1.name);
				}
		;

params_block:	params		{
								validateFunctionCallParams(functions, callFunctionParams, currFunctionCall);
								callFunctionParams.clear();
							}
		;

params:     expr 			{ 
								addCallParamType(callFunctionParams, $1.type);
								fprintf(output, "\tparam %s\n", $1.name); 
							}
        | 	params ',' expr {
								addCallParamType(callFunctionParams, $3.type);								
								fprintf(output, "\tparam %s\n", $3.name); 
							}
        ;
        
/************* RETURN *************/

instReturn:   	RETURN expr ';' {
									validateReturnType(functions, currFunctionDecl, $2.type);
									fprintf(output, "\treturn %s\n", $2.name);
								}
        | 		RETURN ';' 		{
									validateReturnVoid(functions, currFunctionDecl);
									fprintf(output, "\treturn\n");
								}
        ;

/************* PRINT *************/

instPrint:    	PRINT '(' list_instPrint ')' ';'
        ;

list_instPrint:     expr					{ validatePrintArg($1, $$); }
        | 			list_instPrint ',' expr	{ validatePrintArg($3, $$); }
        ;

/************* EXPR *************/

expr:        valueL '=' expr 	{
									validateAssignmentOp($1, $3, $$);
									fprintf(output, "\t%s := %s\n", $1.name, $3.name);
								}
        |    constant			{
									$$.type = $1.type;
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %d\n", $$.name, $1.value.decimal_value); 
        						}
        |    valueL				{ $$.type = $1.type; }
        |    call				{ 
        							$$.type = $1.type; 
        							fprintf(output, "\tcall %s\n", $1.name);
        						}
        |    '(' expr ')'		{ 
									$$.type = $2.type; 
									strcpy($$.name, $2.name); 
								}
        |    expr '+' expr 		{ 
									validateArithOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s + %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '-' expr		{ 
									validateArithOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s - %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '*' expr		{ 
									validateArithOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s * %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '/' expr		{
									validateArithOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s / %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '%' expr		{ 
									validateModuleOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s %% %s\n", $$.name, $1.name, $3.name); 
        						}
        |    '-' expr			{ 
									validateMinusOp($2, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := -%s\n", $$.name, $2.name); 
        						}
        |    expr '<' expr		{ 
									validateRelationEqualOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s < %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr LESSEQTHAN expr { 
									validateRelationEqualOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s <= %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '>' expr		{
									validateRelationEqualOp($1, $3, $$); 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s > %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr MOREEQTHAN expr { 
									validateRelationEqualOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s >= %s\n", $$.name, $1.name, $3.name); 
        						}  
        |    expr EQUALCOMP expr { 
									validateRelationEqualOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s == %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr DIFFCOMP expr { 
									validateRelationEqualOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s != %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr AND expr 		{ 
									validateBoolOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s && %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr OR expr		{ 
									validateBoolOp($1, $3, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := %s || %s\n", $$.name, $1.name, $3.name); 
        						}
        |    '!' expr			{ 
									validateBoolOp($2, $$);
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							fprintf(output, "\t%s := !%s\n", $$.name, $2.name); 
        						}
        |    READINT '(' ')'	{ $$.type = int_type; }
        |    READLINE '(' ')'	{ $$.type = string_type; }
        ;

valueL:     ID 					{ 
									validateLeftValue($1, $$, table);
									strcpy($$.name, $1.name); 
								}
        | 	ID '[' CDEC ']' 	{ 
									validateLeftValue($1, $$, table);
									sprintf($$.name, "%s[%s]", $1.name, $3.name); 
								} // to fix
        ;
        ;

constant:   CDEC	{ $$.type = int_type; $$.value.decimal_value = $1.value.decimal_value; }
		|	CHEX	{ $$.type = int_type; }
        | 	CDOUBLE	{ $$.type = double_type; $$.value.double_value = $1.value.double_value; }
        | 	TRUE 	{ $$.type = bool_type; }
        | 	FALSE	{ $$.type = bool_type; }
        | 	CSTRING { $$.type = string_type; }
        | 	INULL	{ $$.type = pointer_type; }
        ;
void:		VOID	{ $$.type = void_type; }
		;

%%

main(int argc, char **argv)
{
    if(argc != 2)
    {
        cout << "Bad arguments" << endl;
        return 1;
    }

    FILE *file = fopen(argv[1], "r");
    output = fopen("a.out", "w");

    if(!file)
    {
        cout << "Invalid or wrong file" << endl;
        return 1;
    }

    yyin = file;	
    
    // push global scope
    pushScope(table);

    do
    {
        yyparse();
    }
    while(!feof(yyin));

    return 0;
}
